

local skynet = require "skynet"
local ex_log = require "ex_log"

local CMD = {}
local init_fn
local attach_player_fn
local detach_player_fn

local uinmap = {}
local agents = {}
local change_count = 0
local HEART_TIME = 20
local KICK_TIME = 6000

local function sort_load()
    table.sort(agents, function(a, b)
        if a.load == b.load then
            return a.index < b.index
        else
            return a.load < b.load
        end
    end)
    for i, v in agents do
        v.index = i
    end
end

local function add_change_count(count)
    change_count = change_count + 1
    if change_count >= 100 then
        sort_load()
        change_count = 0
    end
end

local function force_detach(player)
    skynet.call(player.agent.svr, "lua", "detach_player", uin)
    add_change_count(1)
    uinmap[player.uin] = nil
    if detach_player_fn then
        detach_player_fn(player)
    end
end


local function check_heartbeat()
    skynet.timeout(500, check_heartbeat)
    local now = os.time()
    local kicked = {}
    for k, v in pairs(uinmap) do
        if (now - v.heart_time) > HEART_TIME and v.kick_time == 0 then
            v.kick_time = now
        end
        if v.kick_time > 0 and (now - v.kick_time) > KICK_TIME then
            skynet.fork(function()
                force_detach(v)
            end)
        end
    end
end


function CMD.attach_player(uin, param)
    local player = uinmap[uin]
    if not player then
        player = {
            uin = uin,
            agent = agents[1],
            heart_time = os.time(),
            kick_time = 0,
        }
        uinmap[uin] = player
        add_change_count(1)
    end
    skynet.send(player.agent.svr, "lua", "attach_player", uin, param)
    if attach_player_fn then
        return attach_player_fn(player, param)
    end
end

function CMD.detach_player(uin)
    local player = uinmap[uin]
    if player then
        force_detach(player)
    end
end

function CMD.get_player(uin)
    return uinmap[uin]
end

function CMD.init(param)
    for i, v in ipairs(agents) do
        skynet.call(v.svr, "lua", "init", param)
    end
    if init_fn then
        init_fn(CMD, param)
    end
    check_heartbeat()
end

return function(param)
    init_fn = param.init_fn
    attach_player_fn = param.attach_player_fn
    detach_player_fn = param.detach_player_fn
    local agent_name = param.agent_name
    local agent_count = param.agent_count
    assert(agent_name)
    agent_count = tonumber(agent_count)
    if agent_count < 1 then
        agent_count = 1
    end
    HEART_TIME = param.heart_time or HEART_TIME
    KICK_TIME = param.kick_time or KICK_TIME

    skynet.start(function()
        for i = 1, agent_count do
            local agent = skynet.newservice(agent_name)
            agents[i] = {
                svr = agent,
                load = 0,
                index = i,
            }
        end
        skynet.dispatch("lua", function(session, source, cmd, ...)
            local f = CMD[cmd]
            assert(f, cmd)
            skynet.retpack(f(...))
        end)
    end)
end
